package cn.net.springboot.netty.learn;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.util.ReferenceCountUtil;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import org.apache.commons.lang3.time.DateFormatUtils;

public class NettyServer {
    public static void main(String[] args) {
        //第一个线程组 一般用于Clent连接
        NioEventLoopGroup bossGroup = new NioEventLoopGroup();
        //第二个线程组  用于实际的工作处理
        NioEventLoopGroup workGroup = new NioEventLoopGroup();
        //新建一个类bootStrap 用于进行对Server进行一系列配置
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        //将两个线程组加入到配置
        serverBootstrap.group(bossGroup, workGroup)
                //指定NioServerSocketChannel通道
                .channel(NioServerSocketChannel.class)
                .handler(new LoggingHandler(LogLevel.INFO))
                //使用childHandler去绑定具体的事件
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel socketChannel) throws Exception {
                        socketChannel.pipeline()
                                // .addLast(new FixedLengthFrameDecoder(5))
                                // .addLast(new StringEncoder(StandardCharsets.UTF_8))
                                // .addLast(new StringDecoder(StandardCharsets.UTF_8))
                                .addLast(new ServerHandler());
                    }
                })
                /**
                 * 在服务器端TCP内核模块维护有2个队列
                 * 客户端向服务端connect的时候,会发送带有SYN标志的包(第一次握手)
                 * 服务端收到客户端发来的SYN时,向客户端发送SYN ACK确认(第二次握手)
                 * 此时TCP会将客户端连接加入到队列1中,当服务端收到收到客户端发送的ACK时(第三次握手)
                 * TCP内核会把客户端连接从1加入到队列2中,连接完成,应用程序的accept会返回
                 * 也就是说accept从队列2中取出完成三次握手的连接
                 * 这两个队列的长度之和为backlog当两个队列的长度,当队列之和大于backlog时,新连接将会被TCP内核拒绝
                 * 所以,如果backlog过小,可能会出现accept速度跟不上,两个队列满了,导致新的客户端连接不上
                 * backlog对程序支持的连接并无影响,backlog影响的只是没有被accept取出的连接
                 */
                //设置tcp连接的缓存区
                // .option(ChannelOption.SO_BACKLOG,128)

                //保持连接
                .childOption(ChannelOption.SO_KEEPALIVE, true);
        //绑定指定的端口 进行监听
        try {
            ChannelFuture f = serverBootstrap.bind(8888).sync();
            Thread.sleep(100000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            bossGroup.shutdownGracefully();
            workGroup.shutdownGracefully();
        }

    }

    static class ServerHandler extends ChannelInboundHandlerAdapter {

        @Override
        public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
            System.out.println(DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss.SSS") + " server ======== channelRegistered ========");
            super.channelRegistered(ctx);
        }

        @Override
        public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
            System.out.println(DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss.SSS") + " server ======== channelUnregistered ========");
            super.channelUnregistered(ctx);
        }

        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            System.out.println(DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss.SSS") + " server ======== channelActive ========");
            super.channelActive(ctx);
        }

        @Override
        public void channelInactive(ChannelHandlerContext ctx) throws Exception {
            System.out.println(DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss.SSS") + " server ======== channelInactive ========");
            super.channelInactive(ctx);
        }

        @Override
        public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
            System.out.println(DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss.SSS") + " server ======== channelReadComplete ========");
            super.channelReadComplete(ctx);
            // ctx.writeAndFlush(Unpooled.copiedBuffer("hello client", StandardCharsets.UTF_8));
        }

        @Override
        public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
            System.out.println(DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss.SSS") + " server ======== userEventTriggered ========");
            super.userEventTriggered(ctx, evt);
        }

        @Override
        public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
            System.out.println(DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss.SSS") + " server ======== channelWritabilityChanged ========");
            super.channelWritabilityChanged(ctx);
        }

        /**
         * @param ctx 上下文
         * @param msg 与服务端设置的解码器一致（.addLast(new StringDecoder(StandardCharsets.UTF_8))）。如 {@link StringEncoder} =>
         *            java.lang.String, 默认为： {@link ByteBuf}({@link
         *            io.netty.buffer.UnpooledUnsafeDirectByteBuf})
         */
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) {
            System.out.println(DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss.SSS") + " server ======== channelRead ========");
            try {
                //do something msg
                ByteBuf buf = (ByteBuf) msg;
                byte[] data = new byte[buf.readableBytes()];
                buf.readBytes(data);
                String request = new String(data, StandardCharsets.UTF_8);
                System.out.println("from cleint : " + request);
                //写给客户端
                ctx.writeAndFlush(Unpooled.copiedBuffer("hello client", StandardCharsets.UTF_8))
                        // 发送主动断开连接
                        .addListener(ChannelFutureListener.CLOSE);
            } finally {
                ReferenceCountUtil.release(msg);
            }

        }

        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            System.out.println(DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss.SSS") + " server ======== exceptionCaught ========");
            cause.printStackTrace();
            ctx.close();
        }
    }

}

// 2019-05-20 15:08:17.591 server ======== channelRegistered ========
// 2019-05-20 15:08:17.650 server ======== channelActive ========
// 2019-05-20 15:08:17.676 server ======== channelRead ========
// Server: 8888
// 2019-05-20 15:08:17.692 server ======== channelReadComplete ========
// 2019-05-20 15:08:17.692 server ======== channelInactive ========
// 2019-05-20 15:08:17.692 server ======== channelUnregistered ========